
package net.badcurrent.ldapodm.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.badcurrent.ldapodm.reflection.ReflectionUtils;
import net.badcurrent.ldapodm.*;
import net.badcurrent.ldapodm.typeconverter.TypeConverter;

import javax.naming.directory.*;
import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <code>Proxy</code> is used to intercept setter calls to mapped objects.  The intercepted
 * data is used to update the <code>UnitOfWork</code> for the object in the
 * <code>Session</code>.
 *
 * @author John D'Emic <john.demic@gmail.com>
 */
public class Proxy implements MethodInterceptor {

    private static Log log = LogFactory.getLog(Proxy.class);

    public Proxy() {
    }

    /**
     * Return a proxied instance for the supplied class.
     * @param clazz
     * @return a proxied instance of the class
     */
    public static Object newInstance(Class clazz) {
        Proxy proxy = new Proxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(proxy);
        return enhancer.create();
    }

    /**
     * Intercept setter calls to update the associated <code>UnitOfWork</code> in the
     * <code>ThreadLocal</code> <code>Session</code>.
     * @param obj
     * @param method
     * @param args
     * @param proxy
     * @return
     * @throws Throwable
     */
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object retValFromSuper;
        retValFromSuper = proxy.invokeSuper(obj, args);

        if (SessionImpl.getThreadSession() == null)
            throw new SessionException("No Session associated with thread: " + Thread.currentThread());

        SessionImplementor session = SessionImpl.getThreadSession();
        String propertyName = ReflectionUtils.getProperty(method);

        if (method.getName().startsWith("set")
                && session.isObjectAttached(obj)
                && session.isLdapProperty(obj.getClass().getSuperclass(), propertyName)) {

            if (log.isDebugEnabled()) log.debug("Adding to Unit of Work");

            String attrValue;

            if (args[0] instanceof String) {
                attrValue = (String) args[0];
            } else {
                TypeConverter typeConverter =
                        session.getTypeConverter(args[0].getClass());
                if (typeConverter == null) {
                    throw new MappingException("No TypeConverter registered for: " + args[0].getClass());
                }
                attrValue = typeConverter.toString(args[0]);
            }

            Attributes attrs = new BasicAttributes();
            for (String attrName : session.getAttributesForProperty(obj.getClass(), propertyName)) {
                attrs.put(new BasicAttribute(attrName, attrValue));
            }
            session.updateUnitOfWork(obj, propertyName, attrs);
        }
        return retValFromSuper;
    }
}
